查看原文
其他

手写spring+springmvc+mybatis框架篇【springIOC容器】

Java面试 2020-10-17

启动IOC容器为initBean方法,下面贴一下这两个类的关系图。


首先是applicationContext


其次是InitBean

XmlApplicationContext :为解析xml文件的类,在spring源码中Resouce接口是用来解析多种文件格式的xml文件的接口,可能参数时inputStream,也可能是byteArray等,但是我们这里比较简单,直接用new File()传递xml文件。


将读取到的对象用如下对象来保存

Map<String, GenericBeanDefinition> beanDefinitionXmlMap =
new ConcurrentHashMap<>(256);

spring也是这么做的。这个也就是我们说的容器。这里介绍一下我定义的这个beanDefinitionXmlMap 对象,beanDefinitionXmlMap 中的key为我们xml文件中的id,value为GenericBeanDefinition 对象。


每一个GenericBeanDefinition 对象其实就代表一个bean,在GenericBeanDefinition 对象中,className就是对应的实体类的class的名字,而一个ChildBeanDefinition 对象就代表一个子元素(一个property:属性注入 或者一个constructor-arg元素:构造器注入)

package spring.factory;

import lombok.Data;
import java.util.List;
/**
* Created by Xiao Liang on 2018/6/29.
* 用来存放xml中注入的bean
*/

@Data
public class GenericBeanDefinition {
   /**
    * className和xml中的class对应
    */

   private String className;

   /**
    *  这是bean下面的属性集合
    */

   private List<ChildBeanDefinition> childBeanDefinitionList;
}


package spring.factory;

import lombok.Data;
/**
* Created by Xiao Liang on 2018/6/27.
*/

@Data
public class ChildBeanDefinition {
   private String childrenType;// 这个是property或者constructor-arg类型
   private String attributeOne;//这个是第一个值
   private String attributeTwo;//这个是第二个值
}


我定义的IOCRULES是用枚举来表示的,下面贴一下我定义的注入规则。

package spring.xmlRules;

import lombok.Getter;
/**
*  @Author xiao liang
* Ioc中xml配置的规则
*/

@Getter
public enum IocRules {
   BEAN_RULE("bean""id""class"),
   SNAN_RULE("component-scan""base-package""null"),
   /**
    * set注入的规则
    */

   SET_INJECT("property""name""value"),
   /**
    * 构造器注入的规则,使用构造器注入的时候必须指定顺序。
    */

   CONS_INJECT("constructor-arg""value""index");
   private String type;
   private String name;
   private String value;
   IocRules(String property, String name, String value) {
       this.type  = property;
       this.name  = name;
       this.value = value;
   }
}

XmlApplicationContext:其实这里最好是只提供接口,然后让子类来实现。但是为了简化,方便起见,直接写在了这里。最关键的是标红的两个方法,一个是getBeanDefinitionMap,一个是getComponentList


第一个是解析xml文件并且将属性值保存在容器(beanDefinitionXmlMap对象)中,第二个是获取一个链表,这个链表是存在注解扫描的并且排列好实例化顺序后的链表。这个扫描+实例化顺序的核心功能在ScanUtil工具类中。这个类也是难点。

package spring.xml;

import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import spring.Utils.StringUtils;
import spring.Utils.scan.ScanUtil;
import spring.constants.Constants;
import spring.exception.XmlException;
import spring.factory.ChildBeanDefinition;
import spring.factory.GenericBeanDefinition;
import spring.xmlRules.IocRules;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by Xiao Liang on 2018/6/28.
* 封装解析xml的方法,模仿Ioc注入 BeanDefinition。实际注入的是GenericBeanDefinition
*/

@Slf4j
public class XmlApplicationContext {
   /**
    * @Description 将xml中的bean元素注入到容器中的方法
    *
    * @return 返回值是指定xml中的bean的容器
    */

   public  Map<String, GenericBeanDefinition> getBeanDefinitionMap(String contextConfigLocation) {
       Map<String, GenericBeanDefinition> beanDefinitionXmlMap = new ConcurrentHashMap<>(256);
       List<Element> elementsList = getElements(contextConfigLocation);
       //遍历每一个bean,注入beanDefinitionMap
       for (Element element :
               elementsList) {
           if (element.getName().equals("bean")){
               //声明一个bean的map,用来盛放当前bean子元素的容器
               GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
               List<ChildBeanDefinition> childBeanDefinitionList = new ArrayList<>();
               String beanId = element.attributeValue(IocRules.BEAN_RULE.getName());
               String beanClass = element.attributeValue(IocRules.BEAN_RULE.getValue());
               //保证子元素确实存在
               if (!StringUtils.isEmpty(beanId) && !StringUtils.isEmpty(beanClass)) {
                   //当前bean的className
                   genericBeanDefinition.setClassName(beanClass);
                   //当前bean的所有子元素
                   List<Element> elements = element.elements();
                   if (elements != null) {
                       for (Element childrenElement :
                               elements) {
                           //如果匹配set注入规则,则注入到容器
                           if (childrenElement.getName().equals(IocRules.SET_INJECT.getType())) {
                               ChildBeanDefinition childBeanDefinition = new ChildBeanDefinition();
                               childBeanDefinition.setChildrenType(IocRules.SET_INJECT.getType());
                               String name = IocRules.SET_INJECT.getName();
                               String value = IocRules.SET_INJECT.getValue();
                               setChildBeanDefinitionByType(childrenElement, childBeanDefinition, name, value, childBeanDefinitionList);
                           }
                           //如果匹配构造器注入规则,则注入到容器
                           else if (childrenElement.getName().equals(IocRules.CONS_INJECT.getType())) {
                               ChildBeanDefinition childBeanDefinition = new ChildBeanDefinition();
                               childBeanDefinition.setChildrenType(IocRules.CONS_INJECT.getType());
                               String name = IocRules.CONS_INJECT.getName();
                               String value = IocRules.CONS_INJECT.getValue();
                               setChildBeanDefinitionByType(childrenElement, childBeanDefinition, name, value, childBeanDefinitionList);
                           }
                       }
                   } else {
                       log.info("{}下面没有子元素", beanId);
                   }
                   genericBeanDefinition.setChildBeanDefinitionList(childBeanDefinitionList);
                   beanDefinitionXmlMap.put(beanId, genericBeanDefinition);
               }
           }
       }
       return beanDefinitionXmlMap;
   }
   /**
    * @Description 根据指定的xml,获得注解扫描的bean容器
    * @param contextConfigLocation
    * @return
    */

   public List<String> getComponentList(String contextConfigLocation){
       List<String> componentList = new ArrayList<>();
       List<Element> elementsList = getElements(contextConfigLocation);
       for (Element element :
               elementsList) {
           if (element.getName().equals(IocRules.SNAN_RULE.getType())) {
               String packageName = element.attributeValue(IocRules.SNAN_RULE.getName());
               componentList.addAll(resolveComponentList(packageName));
           }
       }
       return componentList;
   }
   /**
    * 根据要扫描的包名,返回有注解扫描的类
    * @param packageName
    * @return
    */

   public List<String> resolveComponentList(String packageName){
       if (StringUtils.isEmpty(packageName)){
           throw new XmlException("请正确设置"+IocRules.SNAN_RULE.getType()+"的属性");
       }
       List<String> componentList = new ArrayList<>();
       List<String> componentListAfter = ScanUtil.getComponentList(packageName);
       componentList.addAll(componentListAfter);
       return  componentList;
   }
   /**
    * 将每个bean的子元素注入容器
    *
    * @param element
    * @param childBeanDefinition
    * @param name
    * @param value
    * @param childBeanDefinitionList
    */

   private void setChildBeanDefinitionByType(Element element, ChildBeanDefinition childBeanDefinition, String name, String value,
                             List<ChildBeanDefinition> childBeanDefinitionList) {
       if (childBeanDefinition != null) {
           childBeanDefinition.setAttributeOne(element.attributeValue(name));
           childBeanDefinition.setAttributeTwo(element.attributeValue(value));
           childBeanDefinitionList.add(childBeanDefinition);
       } else {
           throw new XmlException("未按照格式配置xml文件或者暂不支持改配置属性");
       }
   }

   /**
    * 解析xml的工厂,根据路径名获取根元素下面的所有子元素
    * @param contextConfigLocation
    * @return
    */

   private List<Element> getElements(String contextConfigLocation) {
       // 创建saxReader对象
       SAXReader reader = new SAXReader();
       // 通过read方法读取一个文件 转换成Document对象
       Document document = null;
       String pathName = Constants.PATH + contextConfigLocation;
       try {
           document = reader.read(new File(pathName));
       } catch (DocumentException e) {
           log.error("文件没有找到,{}", pathName);
       }
       //获取根节点元素
       Element node = document.getRootElement();
       //获取所有的bean
       List<Element> elementsList = node.elements();
       return elementsList;
   }
}


难点一:ScanUtil类,这个类是spring容器的核心以及难点,我说一下过程,按照这个过程来看代码比较好理解。


分为几个步骤:

  1. 首先通过getClassName获取指定包下的所有类名的集合。

  2. 遍历每个类名的集合,用resolveComponent方法来扫描注解在类上的注解@MyController @MyService @MyRepository,

  3. 如果类上面有这些注解,则开始扫描此类上的属性上有没有@MyAutowired注解,如果存在@MyAutowired注解,则去属性对应的类上面递归上面的过程。直到类中没有@MyAutowired注解的时候。将类名添加到ComponentList链表中。在此过程中,如果类名是接口,并且有实现类的时候,添加到ComponentList链表中的是实现类的名字。为了完成此步骤,我设计了一个makeInterfaceAndImplMap方法用来绑定接口和其实现类,所以此框架目前只支持一个接口,一个实现类。

  4. 在第三步中,如果类上没有注解,并且是一个接口,此时默认是需要动态代理的接口,将此类名直接添加到ComponentList链表中。

package spring.Utils.scan;

import lombok.extern.slf4j.Slf4j;
import spring.Utils.AnnotationUtils;
import spring.Utils.ListAddUtils;
import spring.annotation.MyAutowired;
import spring.annotation.MyController;
import spring.annotation.MyRepository;
import spring.annotation.MyService;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by Xiao Liang on 2018/6/27.
* 扫描工具类(核心方法是getClassName和getComponentList)
* 1 扫描包下的注解
* 2 扫描包下的类名
*/

@Slf4j
public class ScanUtil {
   private static List<String> listClassName = new ArrayList<>();
   private static List<String> componentList = new ArrayList<>();
   private static Map<StringString> interfaceAndImplMap = new ConcurrentHashMap<>();
   /**
    * 扫描指定包下面的所有类名
    *
    * @param packageName,包名
    * @return 类名的集合,
    */

   public static List<String> getClassName(String packageName) {
       Enumeration<URL> urls = null;
       ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
       String newPackageName = packageName.replace(".""/");
       try {
           urls = contextClassLoader.getResources(newPackageName);
           while (urls.hasMoreElements()) {
               URL url = urls.nextElement();
               File packageFile = new File(url.getPath());
               File[] files = packageFile.listFiles();
               if (files == null) {
                   break;
               }
               for (File file :
                       files) {
                   //如果是class,则添加到list中返回
                   if (file.getName().endsWith(".class")) {
                       String templeName = (packageName.replace("/"".") + "." + file.getName());
                       String newTempleName = templeName.substring(0, templeName.lastIndexOf("."));
                       listClassName.add(newTempleName);
                   }
                   //如果是package,则继续遍历
                   else {
                       String nextPackageName = newPackageName + "." + file.getName();
                       getClassName(nextPackageName);
                   }
               }
           }
       } catch (IOException e) {
           e.printStackTrace();
       }
       return listClassName;
   }
   /**
    * 返回 有注解的实例化顺序的链表
    */

   public static List<String> getComponentList(String packageName) {
       //获取所有类
       List<String> classNameList = getClassName(packageName);
       //将扫描的接口和其实现类,使用map对应上,模仿spring接口注入,复杂的原因是java不支持从接口获取实现类
       makeInterfaceAndImplMap(classNameList);
       for (String className :
               classNameList) {
           try {
               //实例化每个类
               resolveComponent(className);
           } catch (ClassNotFoundException e) {
               log.error("扫描注解的时候,{}没有找到", className);
               e.printStackTrace();
           }
       }
       return componentList;
   }
   /**
    * getComponentList();递归调用的子方法
    *
    * @param className
    */

   public static void resolveComponent(String className) throws ClassNotFoundException {
       Class<?> aClass = Class.forName(className);
       //在此处添加要识别的注解,也是每次扫描的顺序,最好遵循习惯
       addNewAnnotation(MyController.class, aClass);
       addNewAnnotation(MyService.class, aClass);
       addNewAnnotation(MyRepository.class, aClass);
   }
   public static <A extends Annotation> void addNewAnnotation(Class<A> annotationClass, Class<?> aClass) throws ClassNotFoundException {
       //如果类上有注解,判断属性上有没有注解
       if (!AnnotationUtils.isEmpty(aClass.getAnnotation(annotationClass))) {
           Field[] fields = aClass.getDeclaredFields();
           if (fields == null || fields.length == 0) {
               ListAddUtils.add(componentList, aClass.getName());
           } else {
               //跳出递归的语句,也就是最底层的类,如果所有属性没有@MyAutowired注解,则注入到链表中
               if (isEmptyAutowired(fields)) {
                   ListAddUtils.add(componentList, aClass.getName());
               } else {
                   //如果属性上有@MyAutowired,则继续递归
                   for (Field field :
                           fields) {
                       //递归具体的查找到底哪个属性上有@MyAutowired。
                       if (field.getAnnotation(MyAutowired.class) != null) {
                           //如果有则根据类名查找类,然后去对应的类中递归此过程
                           String newFieldName = field.getType().getName();
                           //如果是接口,则用其实现类注入
                           if (Class.forName(newFieldName).isInterface()) {
                               String nextName = convertInterfaceToImpl(newFieldName);
                               if (!componentList.contains(nextName)) {
                                   resolveComponent(nextName);
                               }
                           } else {
                               resolveComponent(newFieldName);
                           }
                       }
                   }
                   ListAddUtils.add(componentList, aClass.getName());
               }
           }
       }
       //如果是需要动态的代理注入的接口,加入到实例化的链表中
       else if (aClass.isInterface() && interfaceAndImplMap.get(aClass.getName()).equals("proxy")) {
           ListAddUtils.add(componentList, aClass.getName());
       }
   }
   /**
    * 判断一组属性里面有没有注解
    *
    * @param fields
    * @return
    */

   private static boolean isEmptyAutowired(Field[] fields) {
       for (Field field :
               fields) {
           if (!AnnotationUtils.isEmpty(field.getAnnotation(MyAutowired.class))) {
               return false;
           }
       }
       return true;
   }
   /**
    * 工具类,组装接口和实现类
    *
    * @param classNameList
    * @return
    */

   private static Map<StringString> makeInterfaceAndImplMap(List<String> classNameList) {
       Class<?> aClass = null;
       //interfaceNameList是所有接口类名的链表
       List<String> interfaceNameList = new ArrayList<>();
       //这个链表保存的是有实现类的接口的链表名,默认没有实现类的接口即为需要动态注的链表
       List<String> interfaceExist = new ArrayList<>();
       //循环类名,将类名注入到链表中
       for (String className :
               classNameList) {
           try {
               aClass = Class.forName(className);
           } catch (ClassNotFoundException e) {
               e.printStackTrace();
           }
           if (aClass.isInterface()) {
               interfaceNameList.add(aClass.getName());
           }
       }
       for (String className :
               classNameList) {
           Class<?> bClass = null;
           try {
               bClass = Class.forName(className);
           } catch (ClassNotFoundException e) {
               e.printStackTrace();
           }
           Class<?>[] interfaces = bClass.getInterfaces();
           //如果是接口的实现类
           if (interfaces != null && interfaces.length != 0) {
               for (String interfaceName :
                       interfaceNameList) {
                   for (Class<?> interfaceClass :
                           interfaces) {
                       //如果既有接口,也有实现类,则组成map
                       if (interfaceName.equals(interfaceClass.getName())) {
                           interfaceAndImplMap.put(interfaceName, className);
                           interfaceExist.add(interfaceName);
                       }
                   }
               }
           }
       }
       //需要动态代理注入的接口,在map中用value = proxy来识别
       interfaceNameList.removeAll(interfaceExist);
       if (interfaceNameList != null && interfaceNameList.size() > 0) {
           for (String spareInterfaceName :
                   interfaceNameList) {
               interfaceAndImplMap.put(spareInterfaceName, "proxy");
           }
           System.out.println("已经存在的" + interfaceNameList);
       }
       return null;
   }
   /**
    * 工具类:接口转换为实现类
    *
    * @param newFileName
    * @return
    */

   private static String convertInterfaceToImpl(String newFileName) {
       Set<Map.Entry<StringString>> entries = interfaceAndImplMap.entrySet();
       for (Map.Entry<StringString> entry :
               entries) {
           if (newFileName.equals(entry.getKey()) && !entry.getValue().equals("proxy")) {
               return entry.getValue();
           } else if (newFileName.equals(entry.getKey()) && entry.getValue().equals("proxy")) {
               return entry.getKey();
           }
       }
       return null;
   }
}

这样获得实例化顺序的链表ComponentList之后,开始实例化,也就是initBean这个类。实例化通过反射new Instance()方法获得对象,绑定后的对象用beanContainerMap来保存。


但是这里存在一个问题,就是我们动态代理添加到ComponentList的是接口名称,接口名称不能直接new Instance(),所以这里标红处用的是动态代理实例化的对象,这部分代码是针对Mybatis的接口注入的。先不用管具体是什么意思,后续会讲解。


这里还要注意的是先解析xml还是先解析注解扫描的问题,spring是优先解析xml文件的bean,然后执行的注解注入。和这里的顺序一致。

package spring.factory;

import lombok.extern.slf4j.Slf4j;
import spring.Utils.AnnotationUtils;
import spring.annotation.MyAutowired;
import spring.constants.Constants;
import spring.mybatis.MySqlSession;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by Xiao Liang on 2018/6/27.
*/

@Slf4j
public class InitBean extends BeanDefinition {
   //初始化后的bean容器 key为class名,value为实例化对象
   public Map<StringObject> beanContainerMap = new ConcurrentHashMap<>();
   /**
    * 初始化bean容器方法
    * 注意,扫描的bean会覆盖xml中配置的bean,spring也是这样,扫描的注入和装配都是在xml之后
    * MyAutowired暂时是根据名称装配和扫描
    */

   public void initBeans() {
       //初始化xml配置
       initXmlBeans(Constants.contextConfigLocation);
       initXmlBeans(Constants.springmvcConfigLocation);
       //初始化扫描注解的配置
       initAutowiredBeans(Constants.contextConfigLocation);
   }
   /**
    * 初始化xml中bean内容的方法
    */

   public void initXmlBeans(String contextConfigLocation) {
       ApplicationContext applicationContext = new ApplicationContext(contextConfigLocation);
       Class<?> aClass = null;
       //从容器中取出bean,用application的getbean方法依次加载bean
       Map<String, GenericBeanDefinition> beanDefinitionMap = super.getbeanDefinitionXmlMap(contextConfigLocation);
       Set<Map.Entry<String, GenericBeanDefinition>> entries = beanDefinitionMap.entrySet();
       for (Map.Entry<String, GenericBeanDefinition> entry :
               entries) {
           String beanId = entry.getKey();
           String className = entry.getValue().getClassName();
           try {
               aClass = Class.forName(className);

           } catch (ClassNotFoundException e) {
               log.error("xml中{}无法实例化", className);
               e.printStackTrace();
           }
           beanContainerMap.put(className, aClass.cast(applicationContext.getBean(beanId)));
       }
   }
   /**
    * 将所有的componentList(也就是加注解的类)里面的bean实例化
    *
    * @return
    */

   public void initAutowiredBeans(String contextConfigLocation) {
       List<String> componentList = super.getComponentList(contextConfigLocation);
       System.out.println("实例化的顺序" + componentList);
       //扫描到有注解的类,初始化类的名单
       for (String className :
               componentList) {
           //将每一个类初始化
           try {
               initClass(className);
           } catch (ClassNotFoundException e) {
               log.error("{}没有找到", className);
               e.printStackTrace();
           } catch (IllegalAccessException e) {
               e.printStackTrace();
           } catch (InstantiationException e) {
               e.printStackTrace();
           }
       }
   }
   /**
    * 初始化每一个类的方法,初始化的时候由于spring要实现使用接口注入,所以比较麻烦
    * 需要根据类名来判断是否有接口,然后在将接口名和实现类对应上装配到容器中
    *
    * @param className
    */

   public void initClass(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
       Class<?> aClass = Class.forName(className);
       //先判断这个类有没有接口,如果有接口,将接口装配
       Class<?>[] interfaces = aClass.getInterfaces();
      //如果类是接口,注入的对象是动态代理的对象
       if (aClass.isInterface()){
           MySqlSession mySqlSession = new MySqlSession();
           beanContainerMap.put(aClass.getName(),mySqlSession.getMapper(aClass, Constants.mybatisConfigLocation));
       }
      //如果不是接口的实现类,也就是controller层
       else if (interfaces == null || interfaces.length == 0) {
           noInterfaceInit(className, className);
       }
       else {
           for (Class<?> interfaceClass :
                   interfaces) {
               boolean flag = isExistInContainer(className);
               //容器中如果有,则直接使用这个对象进行装配
               if (flag) {
                   beanContainerMap.put(interfaceClass.getName(), aClass.newInstance());
               } else {
                   //如果容器中没有,则先实例化实现类,然后再装配到容器中
                   noInterfaceInit(className, interfaceClass.getName());
               }
           }
       }
   }
   /**
    * @param className
    * @param interfaceName
    */

   public void noInterfaceInit(String className, String interfaceName) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
       Class<?> aClass = Class.forName(className);
       //bean实例化
       System.out.println("实例化的名字"+aClass.getName());
       Object object = aClass.newInstance();
       Field[] declaredFields = aClass.getDeclaredFields();
       for (Field field :
               declaredFields) {
           //如果属性上有MyAutowired注解,则先将属性注入进去
           if (!AnnotationUtils.isEmpty(field.getAnnotation(MyAutowired.class))) {
               //System.out.println("发现注解");
               //设置私有属性可见
               field.setAccessible(true);
               //如果有注解,在实例化链表里面搜寻类名
               Set<Map.Entry<StringObject>> entries = beanContainerMap.entrySet();
               for (Map.Entry<StringObject> entry :
                       entries) {
                   String type = field.getType().getName();
                   if (entry.getKey().equals(type)){
                       field.set(object, entry.getValue());
                   }
               }
           }
       }
       beanContainerMap.put(interfaceName, object);
   }
   /**
    * 属于工具类,不是很重要
    * 在实例化该类之前先判断该类在容器中是否存在
    *
    * @param className
    * @return
    */

   public boolean isExistInContainer(String className) {
       Set<Map.Entry<StringObject>> entries = beanContainerMap.entrySet();
       if (entries != null) {
           for (Map.Entry<StringObject> map :
                   entries) {
               if (map.getKey().equals(className)) {
                   return true;
               } else {
                   return false;
               }
           }
       }
       return false;
   }
}

至此,spring容器的开发暂时告一段落,下一篇介绍springmvc的实现。


我将此项目上传到了github,需要的童鞋可以点击下方阅读原文自行下载。


扩展阅读

Spring注解@Resource和@Autowired区别对比

Spring4+Spring MVC+MyBatis整合思路

Spring boot Mybatis 整合(完整版)


来源:https://blog.csdn.net/qq_27631217/article/details/80975621

版权声明:文章来源网络,版权归作者本人所有,如侵犯到原作者权益,请与我们联系删除或授权事宜

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存